#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>

MODULE_LICENSE("GPL");

#define PROCFS_NAME "fibocount"
#define FILE_SIZE 4

/* Declaracion de variables globales */

int acum_lecturas = 0;
int acum_escrituras = 0;
static unsigned long prev_fib1, prev_fib2;

static struct proc_dir_entry *proc_entry;
static struct miscdevice mi_dev;

// nos dice si alguien ya abrio el driver,
// ya que es de acceso exclusivo
int device_open = 0;

	/* Prototipos de las funciones de inicializacion y destruccion */

static int __init fibo_init(void);
static void __exit fibo_exit(void);

	/* Prototipos de las operaciones sobre el dispositivo */

static ssize_t fibo_read(struct file *filp, char *buffer, size_t length,loff_t *offset);
static ssize_t fibo_write(struct file *, const char *, size_t, loff_t *);
static int fibo_open(struct inode *inode, struct file *file);
static int fibo_release(struct inode *inode, struct file *file);

	/* Prototipos de las operaciones sobre fibocount */

static int cant_lecturas(char * page, char **start, off_t offset, int count, int *eof, void *data);

	/* Mis funciones auxiliares */

void updateBuffer(void);
int Segundo(const char* s, const char** n2, int size);
unsigned long Numero(const char* s, int size);
unsigned long next_fibo(unsigned long m_w, unsigned long m_z);

	/* defino las funciones de inicializacion y destruccion como tales */

module_init(fibo_init);
module_exit(fibo_exit);

	/* defino mi estructura de operaciones */

static struct file_operations fops =
{
	.read = fibo_read,
	.write = fibo_write,
	.open = fibo_open,
	.release = fibo_release
};

	/* definicion de las operaciones */

static int __init fibo_init(void)
{
	int ret;

	mi_dev.minor = MISC_DYNAMIC_MINOR;
	mi_dev.name = "fibonacci";
	mi_dev.fops = &fops;

	// Registro el misc device /dev/fibonacci

	ret = misc_register(&mi_dev);

	if (ret) return ret;

	// Creamos la entrada en /proc
	
	proc_entry = create_proc_entry(PROCFS_NAME, 0666, NULL);

	// chequeamos que se haya creado bien
	if (proc_entry == NULL)
	{
		remove_proc_entry(PROCFS_NAME,NULL);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",PROCFS_NAME);
		return -ENOMEM;
	}
	
	// Asociamos datos y la funcione de lectura al archivo virtual fibocount
	proc_entry->read_proc = cant_lecturas;			// funcion de lectura
	proc_entry->mode= S_IFREG | S_IRUGO;				// premisos: r--r--r--
	proc_entry->uid= 0;								// usuario owner (root)
	proc_entry->gid= 0;								// grupo owner (root)
	// tamanio del archivo cuando se lista. Ponemos un 4
	// ya que es el tamanio que ocupa el entero que queremos mostrar.
	proc_entry->size= FILE_SIZE;
	
    printk(KERN_ALERT "Se carga el modulo fibonacci con minor %i\n",mi_dev.minor);
    
    return 0;
}

static void __exit fibo_exit(void)
{
	int ret;

	// remuevo el misc device /dev/fibonacci
	
	ret = misc_deregister(&mi_dev);

	// Remuevo el archivo virtual /proc/fibocount
	
	remove_proc_entry(PROCFS_NAME,NULL);

	printk(KERN_ALERT "Se descarga el modulo fibonacci\n");
}

static int fibo_open(struct inode *inode, struct file *file)
{
	if (device_open)
		return -EBUSY;
	device_open++;

	try_module_get(THIS_MODULE);
	return 0;
}

static ssize_t fibo_write(struct file* file, const char* buffer, size_t size, loff_t* data)
{
	int size2; const char* s2; s2=NULL;
	
	size2 = Segundo(buffer,&s2,size);
	
	prev_fib1=Numero(buffer,size);
	
	prev_fib2=Numero(s2,size2);

	acum_escrituras++;
	
	return size;
}

static int fibo_release(struct inode *inode, struct file *file)
{
	device_open--;
	module_put(THIS_MODULE);
	return 0;
}

static ssize_t fibo_read(struct file *filp, char *buffer, size_t length,loff_t *offset)
{
	unsigned long new_fib = next_fibo(prev_fib1,prev_fib2);

	prev_fib1=prev_fib2;
	prev_fib2=new_fib;

	acum_lecturas++;
	
	return sprintf(buffer,"%li\n",new_fib);
}

	/* definicion de las operaciones sobre el archivo de proc */

static int cant_lecturas(char * page, char **start, off_t offset, int count, int *eof, void *data)
{
	int len;
	len = sprintf(page, "Lecturas realizadas: %d\nEscrituras realizadas: %d\n", acum_lecturas,acum_escrituras);
	return len;
}

	/* deinicion de las funciones auxiliares */

// dados dos numeros, clacula el siguiente segun la regla de fibonacci

unsigned long next_fibo(unsigned long m_w, unsigned long m_z)
{
	return (m_z + m_w);
}

// guarda en n2 el puntero al string del segundo numero y devuelve su tamanio

int Segundo(const char* s, const char** n2, int size)
{
	int i;
	for (i=0;i<size;i++)
		if ( s[i]==' ' )
		{
			*n2 = s+i+1;
			return size-i-1;
		}
	return 0;
}

// convierte al string apuntado por s en un numero

unsigned long Numero(const char* s, int size)
{
	unsigned long res=0;
	int i;
	
	for(i=0;i<size;i++)
	{
		int c=(int)s[i];
		
		if ( 48<=c && c<58 )
			res += c-48;
		else
			return res;
	}
	return res;
}
